/*
  Copyright (C) 2011, Kyungjoo Kim
  
  This file is part of LINAL (LINear ALgebra)
  
  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  Also add information on how to contact you by electronic and paper mail.
  
  
  Kyungjoo Kim
  iamkyungjoo@gmail.com
*/
#ifndef LINAL_DEVICE_DEVICE_HXX
#define LINAL_DEVICE_DEVICE_HXX

enum {LINAL_DEVICE_HOST=0,
      LINAL_DEVICE_GPU,
      LINAL_DEVICE_TI};

enum {LINAL_DEVICE_RETURN_FALSE=0,
      LINAL_DEVICE_RETURN_TRUE,
      LINAL_DEVICE_RETURN_NOT_SUPPORT};

enum {LINAL_DEVICE_OP_CHOL_NOPIV=0,
      LINAL_DEVICE_OP_CHOL_PIV,
      LINAL_DEVICE_OP_LDL_NOPIV,
      LINAL_DEVICE_OP_LDL_PIV,
      LINAL_DEVICE_OP_LU_NOPIV,
      LINAL_DEVICE_OP_LU_PIV,
      LINAL_DEVICE_OP_QR, 
      LINAL_DEVICE_OP_GEMM, 
      LINAL_DEVICE_OP_HERK,
      LINAL_DEVICE_OP_TRMM, 
      LINAL_DEVICE_OP_TRSM,
      LINAL_DEVICE_OP_SYRK,
      LINAL_DEVICE_OP_NUM};

enum {LINAL_DEVICE_CACHE_NO_USE=0,
      LINAL_DEVICE_CACHE_READ_ONLY,
      LINAL_DEVICE_CACHE_READ_WRITE};
      
namespace linal {

  // -------------------------------------------------------------- 
  // ** Abstract interfaces for the devices
  class Device_ : public Disp_,
                  public Operation_ {
  private:
  protected:
    omp_lock_t _omp;
    Int_ _id, _group, _type,  // id
      _cache,                 // cache policy
      _balancer,              // balancer
      _cnt,                   // counter
      _stat;
    
    // stat
    Int_ _s_cache, _s_device;
    Int_ _s_ops[20];

  public:

    Device_() : 
      _id(0), _group(0), _type(0),
      _cache(0), _balancer(0), _cnt(1), _stat(0) {  }
    Device_(Int_ id, Int_ group, Int_ type) : 
      _id(id), _group(group), _type(type), 
      _cache(0), _balancer(0), _cnt(1), _stat(0) { }
    
    virtual ~Device_() { }

    inline virtual void set(Int_ id, Int_ group, Int_ type) {
      _id    = id;
      _group = group;
      _type  = type;
    }
    
    // balancer = 1 => everything is inherited
    // balancer = 0 => nothing is inherited
    // balancer = a number => _cnt%_balancer == 0 inherited
    inline virtual void initialize( Int_ balancer=0, Int_ cache=0 ) {
      _cache     = cache;
      _balancer  = balancer;
      omp_init_lock( &_omp );
    }
    inline virtual void finalize() { 
      omp_destroy_lock( &_omp );
      _balancer    = 0;
      _cache     = 0;
    }

    virtual std::ostream& content(std::ostream& os) const {
      return (os 
              << "- Device - \n"
              << "  type [" <<_type 
              << "], id [" << _id 
              << "], group [" << _group 
              << "], balancer [" << _balancer 
              << "]" << std::endl);
    }

    virtual void critical_begin()=0; 
    virtual void critical_end()=0;   
    virtual void invalidate(FLA_Obj &A)=0;

    // balancer contain the thread number who try to lock
    inline void lock() { 
#pragma omp atomic
      ++_cnt;

      critical_begin();
    }
    inline void unlock() { 
      critical_end();
    }
    inline void inherit() {
#pragma omp atomic
      ++_cnt;
    }

    inline Bool_ is_inheritable() {
      return (_balancer ? !(_cnt%_balancer) : false);
    }

    inline Int_ get_id()           { return _id;      }
    inline Int_ get_group()        { return _group;   }
    inline Int_ get_type()         { return _type;    }
    inline Int_ get_cache()        { return _cache;    }

    inline Int_ get_stat()         { return _stat;    }

    // negative value reset the values
    inline void stat_begin() {
      _s_cache = 0;
      _s_device = 0;
      for (Int_ i=0;i<20;++i)
        _s_ops[i] = 0;
      _stat = true;
    }
    inline void stat_end() {
      _stat = false;
    }

    inline void statistics( Int_ &s_cache, Int_ &s_device, Int_ *ops ) {
      s_cache = _s_cache;
      s_device = _s_device;
      for (Int_ i=0;i<LINAL_DEVICE_OP_NUM;++i)
        ops[i] = _s_ops[i];
    }
    virtual std::ostream& statistics(std::ostream& os) const {
      if (_cache) {
        os << "- Cache -\n";
        os << "  cache policy [" << _cache << "]\n";
        os << "  cache hit [" << _s_cache << "], device hit [" << _s_device << "]" << std::endl;
      }

      os << "- Operation -\n";
      os << "  chol nopiv [" << _s_ops[LINAL_DEVICE_OP_CHOL_NOPIV] << "]" << std::endl;
      os << "  chol piv   [" << _s_ops[LINAL_DEVICE_OP_CHOL_PIV] << "]" << std::endl;
      os << "  ldl nopiv  [" << _s_ops[LINAL_DEVICE_OP_LDL_NOPIV] << "]" << std::endl;
      os << "  ldl piv    [" << _s_ops[LINAL_DEVICE_OP_LDL_PIV] << "]" << std::endl;
      os << "  lu nopiv   [" << _s_ops[LINAL_DEVICE_OP_LU_NOPIV] << "]" << std::endl;
      os << "  lu piv     [" << _s_ops[LINAL_DEVICE_OP_LU_PIV] << "]" << std::endl;
      os << "  qr         [" << _s_ops[LINAL_DEVICE_OP_QR] << "]" << std::endl;
      os << "  gemm       [" << _s_ops[LINAL_DEVICE_OP_GEMM] << "]" << std::endl;
      os << "  herk       [" << _s_ops[LINAL_DEVICE_OP_HERK] << "]" << std::endl;
      os << "  trmm       [" << _s_ops[LINAL_DEVICE_OP_TRMM] << "]" << std::endl;
      os << "  trsm       [" << _s_ops[LINAL_DEVICE_OP_TRSM] << "]" << std::endl;
      os << "  syrk       [" << _s_ops[LINAL_DEVICE_OP_SYRK] << "]" << std::endl;
      return os;
    }

    // dummy operations
    void chol_nopiv_stat  () { _s_ops[LINAL_DEVICE_OP_CHOL_NOPIV] +=_stat; }
    void chol_piv_stat    () { _s_ops[LINAL_DEVICE_OP_CHOL_PIV]   +=_stat; }
    void ldl_nopiv_stat   () { _s_ops[LINAL_DEVICE_OP_LDL_NOPIV]  +=_stat; }
    void ldl_piv_stat     () { _s_ops[LINAL_DEVICE_OP_LDL_PIV]    +=_stat; }
    void lu_nopiv_stat    () { _s_ops[LINAL_DEVICE_OP_LU_NOPIV]   +=_stat; }
    void lu_piv_stat      () { _s_ops[LINAL_DEVICE_OP_LU_PIV]     +=_stat; }
    void qr_stat          () { _s_ops[LINAL_DEVICE_OP_QR]         +=_stat; }
    void gemm_stat        () { _s_ops[LINAL_DEVICE_OP_GEMM]       +=_stat; }
    void herk_stat        () { _s_ops[LINAL_DEVICE_OP_HERK]       +=_stat; }
    void trmm_stat        () { _s_ops[LINAL_DEVICE_OP_TRMM]       +=_stat; }
    void trsm_stat        () { _s_ops[LINAL_DEVICE_OP_TRSM]       +=_stat; }
    void syrk_stat        () { _s_ops[LINAL_DEVICE_OP_SYRK]       +=_stat; }

    // virtual interface
    virtual Int_ chol_nopiv  ( Int_ uplo,  FLA_Obj &A )              { return false; }
    virtual Int_ chol_piv    ( Int_ uplo,  FLA_Obj &A,  FLA_Obj &p ) { return false; }
    virtual Int_ ldl_nopiv   ( Int_ uplo,  FLA_Obj &A )              { return false; }

    virtual Int_ ldl_piv     ( Int_ uplo,  FLA_Obj &A,  FLA_Obj &p ) { return false; }
    virtual Int_ lu_nopiv    ( FLA_Obj &A )                          { return false; }

    virtual Int_ lu_piv      ( FLA_Obj &A,  FLA_Obj &p )             { return false; }
    virtual Int_ qr          ( FLA_Obj &A,  FLA_Obj &T )             { return false; }

    virtual Int_ get_diag( FLA_Obj &A, FLA_Obj &d )                  { return false; }
    virtual Int_ set_diag( FLA_Obj &d, FLA_Obj &A )                  { return false; }

    virtual Int_ gemm( Int_ transa,  Int_ transb,
                       FLA_Obj &alpha,  FLA_Obj &A,  FLA_Obj &B,
                       FLA_Obj &beta,   FLA_Obj &C )                 { return false; }
    virtual Int_ herk( Int_ uplo,  Int_ trans,
                       FLA_Obj &alpha,  FLA_Obj &A,
                       FLA_Obj &beta,   FLA_Obj &C )                 { return false; }
    virtual Int_ trmm( Int_ side,  Int_ uplo, Int_ trans, Int_ diag,
                       FLA_Obj &alpha,  FLA_Obj &A,  FLA_Obj &B )    { return false; }
    virtual Int_ trsm( Int_ side,  Int_ uplo, Int_ trans, Int_ diag,
                       FLA_Obj &alpha,  FLA_Obj &A,  FLA_Obj &B )    { return false; }
    virtual Int_ syrk( Int_ uplo,  Int_ trans,
                       FLA_Obj &alpha,  FLA_Obj &A,
                       FLA_Obj &beta,   FLA_Obj &C )                 { return false; }

  };
}

#endif
